package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.product.api.domain.Product;
import com.spzx.product.api.domain.ProductDetails;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPrice;
import com.spzx.product.api.domain.vo.SkuQuery;
import com.spzx.product.api.domain.vo.SkuStockVo;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品Service业务层处理
 */
@Slf4j
@Service
@Transactional
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private ProductDetailsMapper productDetailsMapper;

    @Autowired
    private SkuStockMapper skuStockMapper;

    //@Autowired
    //private StringRedisTemplate stringRedisTemplate; //适合 key和value都是字符串类型

    @Autowired
    private RedisTemplate redisTemplate; //适合值是任意类型

    /**
     * 查询商品列表
     *
     * @param product 商品
     * @return 商品
     */
    @Override
    public List<Product> selectProductList(Product product) {
        return productMapper.selectProductList(product);
    }

    //原子性
    @Override
    public int insertProduct(Product product) {
        //1.保存Product对象到product表
        productMapper.insert(product); //主键回填

        //2.保存List<ProductSku>对象到product_sku表
        List<ProductSku> productSkuList = product.getProductSkuList();
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        int size = productSkuList.size();
        for (int i = 0; i < size; i++) {
            ProductSku productSku = productSkuList.get(i);
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setSkuName(product.getName() + " " + productSku.getSkuSpec());
            productSku.setProductId(product.getId());
            productSkuMapper.insert(productSku);

            //添加商品库存  //3.保存List<SkuStock>对象到sku_stock表
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //4.保存ProductDetails对象到product_details表
        ProductDetails productDetails = new ProductDetails();
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetails.setProductId(product.getId());
        productDetailsMapper.insert(productDetails);

        return 1;
    }


    @Override
    public Product selectProductById(Long id) {
        //1.根据id查询Product对象
        Product product = productMapper.selectById(id);

        //2.封装扩展字段：查询商品对应多个List<ProductSku>
        //select * from product_sku where product_id =?
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));
        List<Long> productSkuIdList = productSkuList.stream().map(productSku -> productSku.getId()).toList();


        // select * from sku_stock where sku_id in (1,2,3,4,5,6)
        List<SkuStock> skuStockList = skuStockMapper.selectList(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        Map<Long, Integer> skuIdToTatalNumMap = skuStockList.stream().collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));
        productSkuList.forEach(productSku -> {
            //返回ProductSku对象，携带了库存数据；
            productSku.setStockNum(skuIdToTatalNumMap.get(productSku.getId()));
        });

        product.setProductSkuList(productSkuList);

        //3.封装扩展字段：商品详情图片List<String>
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
        String imageUrls = productDetails.getImageUrls();   //url,url,url
        String[] urls = imageUrls.split(",");
        product.setDetailsImageUrlList(Arrays.asList(urls));
        //返回Product对象
        return product;
    }


    @Override
    public int updateProduct(Product product) {

        /*要保证缓存和数据库的数据一致性问题,就需要使用延时双删策略,就是在修改数据库前先删除一边缓存,这是为了不让
        * 用户在数据库更新前拿到旧数据
        * */

        //这是第一遍删除缓存的操作,直接删除即可
        //1 删除缓存
        List<Long> skuIdList =  product.getProductSkuList().stream()
                .map(ProductSku::getId).collect(Collectors.toList());
        skuIdList.forEach(skuId -> {
            String dataKey = "product:sku:" + skuId;
            this.redisTemplate.delete(dataKey);
        });

        //1.更新Product
        productMapper.updateById(product);

        //2.更新SKU   List<ProductSku>
        List<ProductSku> productSkuList = product.getProductSkuList();
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        productSkuList.forEach(productSku -> {
            productSkuMapper.updateById(productSku);

            //3.更新库存   List<ProductSku> -> 获取扩展字段stockNum
            SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setAvailableNum(skuStock.getTotalNum() - skuStock.getLockNum());
            skuStockMapper.updateById(skuStock);
        });

        //4.更新详情ProductDetails
        ProductDetails productDetails = productDetailsMapper
                .selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);



        //这是第二次删除缓存
        //4 再次执行操作删除缓存
        skuIdList.forEach(skuId -> {
            String dataKey = "product:sku:" + skuId;
            this.redisTemplate.delete(dataKey);
        });

        return 1;
    }


    @Override
    public int deleteProductByIds(Long[] ids) {
        //1.删除Product表数据
        // delete from product where id in (1,2)
        productMapper.deleteBatchIds(Arrays.asList(ids));

        //2.删除ProductSku表数据
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, Arrays.asList(ids)));
        List<Long> productSkuIdList = productSkuList.stream().map(ProductSku::getId).toList();
        productSkuMapper.deleteBatchIds(productSkuIdList);

        //3.删除SkuStock表数据
        skuStockMapper.delete(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        //4.删除ProductDetails表数据
        // delete from product_details where product_id in (1,2)
        productDetailsMapper.delete(new LambdaQueryWrapper<ProductDetails>().in(ProductDetails::getProductId, Arrays.asList(ids)));
        return 1;
    }


    @Override
    public void updateAuditStatus(Long id, Integer auditStatus) {
        Product product = new Product();
        product.setId(id);
        if (auditStatus == 1) {
            product.setAuditStatus(1);
            product.setAuditMessage("审批通过");
        } else {
            product.setAuditStatus(-1);
            product.setAuditMessage("审批拒绝");
        }
        productMapper.updateById(product);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStatus(Long id, Integer status) {
        Product product = new Product();
        product.setId(id);
        if (status == 1) {
            product.setStatus(1);
            String key = "sku:product:data";
            List<ProductSku> productSkuList = productSkuMapper
                    .selectList(new LambdaQueryWrapper<ProductSku>()
                            .eq(ProductSku::getProductId, id));
            productSkuList.forEach(item -> {
                redisTemplate.opsForValue().setBit(key,item.getId(),true);
            });
        } else {
            product.setStatus(-1);
        }
        productMapper.updateById(product);
    }


    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.getTopSale();
    }


    @Override
    public List<ProductSku> skuList(SkuQuery skuQuery) {
        return productSkuMapper.skuList(skuQuery);
    }


    /**
     * 服务提供者：6个接口来服务于商品详情查询。需要进行优化，提供查询效率。
     * 需要使用redis来提高性能。
     */

    //---------------------------start-----------------------------

    /*
    *
    * 解决缓存问题的步骤:
    *   一、首先加缓存来缓解数据库压力
    *       1.先判断再缓存中有没有这个对象的key,如果没有就去差数据库,如果有就走缓存直接返回
    *           1.1 构建业务数据Key 形式：前缀+业务唯一标识  String dataKey = "product:sku:" + skuId;
    *           1.2 查询Redis获取业务数据
    *           1.3 命中缓存则直接返回
    *       2.缓存中没有数据,去查数据库,把数据查出来
    *           2.1 由于考虑到集群部署项目,所以需要分布式锁来控制查询数据库,还考虑到热点key失效导致的缓存击穿问题,所以使用分布式锁来解决
    *           2.2 构建分布式锁 String lockKey = "product:sku:lock:" + skuId;
    *           2.3 采用UUID作为线程标识 String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
    *           2.4 获取锁失败,睡眠加递归实现自旋锁
    *           2.5 抢锁成功执行正常查询数据库的任务并存入缓存
    *           2.6 别忘了释放锁
    * */

    @Override
    public ProductSku getProductSku(Long skuId) {

        try {
            //定义在缓存中存储对象的key
            String dataKey = "product:sku:" + skuId;

            //查询Redis获取业务数据
            ProductSku productSku = (ProductSku) redisTemplate.opsForValue().get(dataKey);

            //然后在走DB之前先判断redis中有没有这个key
            if (redisTemplate.hasKey(dataKey)){
                log.info("命中缓存,缓存中存在数据，直接返回，线程ID：{}，线程名称：{}直接返回",Thread.currentThread().getId(), Thread.currentThread().getName());
                return productSku;
            }

            //走到这里说明缓存中不存在这个key,那就需要走数据库把数据查出来
            //在此之前要考虑到缓存击穿问题(高并发场景下热点key失效的问题)
            //所以需要构建分布式锁

            //构建分布式锁
            String lockKey = "product:sku:lock:" + skuId;
            //采用UUID作为线程标识
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");

            //利用Redis提供set nx ex 获取分布式锁
            //此行代码视为抢锁,因为setnx,只要key存在那就不可以设置新值,因为setnx中的key只能被设置一次
            Boolean hasLock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

            if (!hasLock){
                //走到这里说明没抢到锁所以需要自旋重复抢锁,此外还需要睡眠片刻再抢锁,抢锁由递归实现,递归出口是return
                //解释: 为什么递归出口是return,因为就算没抢到锁,当别人抢到了锁查出数据然后放到缓存中后,执行递归代码,发现缓存里已经有数据了
                //这时就无需再抢锁,直接return从缓存中查出来得数据即可结束递归
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new ServiceException(e.getMessage());
                }

                log.error("获取锁失败，自旋：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                //这就是递归的代码,就算第一次没抢到锁,递归后也能直接查到数据
                return this.getProductSku(skuId);
            }else {
                //走到这里说明抢到了锁,可以执行正常得业务代码了
                //业务:查询数据库,然后将查出来得数据放到缓存中,最后直接返回查出来得数据
                //抢锁成功,说明你是众多请求中的救世主,万里挑一的存在,你需要查出数据并且存到缓存当中,拯救众请求于水火
                try {
                    log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                    productSku = this.getSkuFromDB(skuId);

                    //首先存入缓存
                    //设置过期时间,防止缓存击穿的方式一: 就算是查出来为null,也需要存,如果是null就设置一个60分钟的过期时间,如果不是10小时
                    long ttl = productSku == null ? 1 * 60 : 10 * 60;

                    //顺便解决一下缓存雪崩问题
                    Random random = new Random();
                    int i = random.nextInt(301);
                    redisTemplate.opsForValue().set(dataKey,productSku,ttl+i,TimeUnit.SECONDS);

                    //最后返回数据
                    return productSku;
                } finally {
                    //最后的最后,别忘了释放锁,建议放到finally语句块中
                    //4.业务执行完毕释放锁
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";

                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(scriptText);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
                }
            }
        } catch (Exception e){
            //兜底处理方案：Redis服务有问题，将业务数据获取自动从数据库获取
            log.error("[商品服务]查询商品信息异常：{}", e);
            return getSkuFromDB(skuId);
        }
    }
    private ProductSku getSkuFromDB(Long skuId) {
        return productSkuMapper.selectById(skuId);
    }


    @Override
    public Product getProduct(Long id) {
        try {
            //构建业务数据key
            String dataKey = "product:id:" + id;

            //从redis中获取数据
            Product product = (Product) redisTemplate.opsForValue().get(dataKey);

            if (redisTemplate.hasKey(dataKey)){
                //走到这说明缓存中有数据直接返回
                log.info("命中缓存,缓存中存在数据，直接返回");
                return product;
            }

            //下面实现分布式锁代码

            String lockKey = "product:id:lock:" + id;
            String lockVal= UUID.randomUUID().toString().replaceAll("-","");

            //利用setnx获取分布式锁
            Boolean hasLock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

            if (!hasLock){
                //走到这说明没抢到锁,锁自旋
                log.info("获取锁失败，自旋");
                Thread.sleep(100);
                return this.getProductFromDB(id);
            }else {
                try {
                    //走到这说明抢到锁了,执行正常逻辑
                    log.info("获取锁成功");
                    product = this.getProductFromDB(id);

                    long ttl = product == null ?  1 * 60 : 10 * 60;

                    //解决缓存雪崩问题
                    Random random = new Random();
                    int i = random.nextInt(301);
                    redisTemplate.opsForValue().set(dataKey,product,ttl + i, TimeUnit.SECONDS);
                    return product;
                } finally {
                    //释放锁
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";

                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(scriptText);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
                }
            }

        } catch (Exception e){
            log.error("[商品服务]查询商品信息异常：{}", e);
            return getProductFromDB(id);
        }
    }
    private Product getProductFromDB(Long id) {
        return productMapper.selectById(id);
    }


    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        ProductSku productSku = productSkuMapper.selectOne(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getId, skuId).select(ProductSku::getSalePrice, ProductSku::getMarketPrice));
        SkuPrice skuPrice = new SkuPrice();
        BeanUtils.copyProperties(productSku, skuPrice);
        return skuPrice;
    }


    @Override
    public ProductDetails getProductDetails(Long id) {
        return productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
    }


    @Override
    public Map<String, Long> getSkuSpecValue(Long id) {
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id).select(ProductSku::getId, ProductSku::getSkuSpec));
        Map<String, Long> skuSpecValueMap = new HashMap<>();
        productSkuList.forEach(item -> {
            skuSpecValueMap.put(item.getSkuSpec(), item.getId());
        });
        return skuSpecValueMap;
    }


    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        BeanUtils.copyProperties(skuStock, skuStockVo);
        return skuStockVo;
    }



    //---------------------------end-----------------------------

    // select * from product_sku where id in (1,2,3)
    // select id,sale_price,market_price from product_sku where id in (1,2,3)
    @Override
    public HashMap<Long, SkuPrice> getSkuPriceList(List<Long> skuIdList) {

        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();

        wrapper.in(ProductSku::getId,skuIdList);

        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        HashMap<Long, SkuPrice> skuPriceHashMap = new HashMap<>();

        for (ProductSku productSku : productSkuList) {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(productSku.getId());
            skuPrice.setSalePrice(productSku.getSalePrice());
            skuPrice.setMarketPrice(productSku.getMarketPrice());
            skuPriceHashMap.put(productSku.getId(),skuPrice);
        }



        return skuPriceHashMap;
    }


    @Transactional(rollbackFor = {Exception.class})
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        /*
        因为如果要使用feign接口远程调用这个方法的话,如果设定了feign接口的重试功能,就会重复发请求,就会重复调用这个接口
        但是这个接口(检查与锁定)的操作必须保证幂等性要求,即不论发送几次请求,只执行一次功能代码
        那要如何保证代码只执行一次并且防止feign接口重试呢?
        就可以用分布式锁来实现,即抢到锁的就可以执行检查和锁定的方法,没抢到锁的就被阻塞,知道超过了feign远程调用的请求最大时常
        但是光有分布式锁还不行,有以下场景:
            1. 第一个请求抢到了分布式锁,开始执行功能代码(调查和锁单功能),第二个请求过来了.
            2. 但是由于分布式锁的存在,第二个请求就没抢到锁,没抢到锁就会分为两种情况,一种就是第一个请求执行完了,将库存成功锁定了,那么第二个请求就无需再
               阻塞了,直接返回完成(空字符串)即可,因为两次请求在幂等之后本质上就是同一个请求,都是为了完成同一个功能,第一个完成了当然可以视为第二个请求也完成了
            3. 第二种情况就是,第一个请求还没执行完业务代码,即还没完成锁单,第二个请求就阻塞住了,那么这时它不知道该返回什么,这时就可以通过缓存来解决这个问题
            4. 如何操作呢? 具体就是,如果第一个抢到锁的请求在完成锁单任务之后,就把锁单的订单和商品信息存到缓存当中,如果第二个请求被阻塞住了,可以根据能否在缓存
               中取到刚刚存入的数据来判断应该返回什么?比如,如果能取到缓存中的数据,就是情况1返回空串,如果取不到,就返回"重复提交"这个字符串

        然后就可以开始写验库存和锁单的功能了
        */

        //1.首先整个分布式锁出来
        String lockKey = "sku:checkAndLock:" + orderNo;
        //再整个最终要存放锁单数据的key
        String dataKey = "sku:lock:data:" + orderNo;

        //2.进行拿锁操作
        Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(dataKey, orderNo, 1, TimeUnit.HOURS);

        //3.判断抢没抢到锁
        if (!isLocked){
            //那就分为两种情况了
            if (redisTemplate.hasKey(dataKey)){
                //1.缓存里有锁单的数据, 返回空串
                return "";
            }else {
                //2.缓存里没有锁单的数据, 视为重复提交
                return "重复提交";
            }
        }

        //4.可以写检查库存的代码了,检查代码的sql语句里加入了行锁,可以保证一次事务内的数据一致性,所以该方法要加事务注解
        //检查库存的代码可以直接调用skuStockMapper的check方法
        //在此说一下这个方法: SkuStock check(@Param("skuId") Long skuId,@Param("skuNum")  Integer skuNum);
        //它的返回值是一个 SkuStock 对象,意思就是,根据这个sql语句查出来的是一行数据,如果有库存就能查到,没库存就查不到
        //查到库存后就封装到了SkuStock对象中,到时候可以根据这个对象是否为null来判断有没有库存
        //可以看到这个方法需求的参数是skuId和skuNum,这两个参数都在这个方法接收的参数skuLockVoList中

        //遍历skuLockVoList查出每种商品是否有库存
        for (SkuLockVo skuLockVo : skuLockVoList) {
            SkuStock skuStock = skuStockMapper.check(skuLockVo.getSkuId(), skuLockVo.getSkuNum());

            //skuStock就是返回来的对象
            if (skuStock == null){
                //走到这说明没对象,对这次遍历的VO对象的isHaveStock(是否有库存)来赋false
                skuLockVo.setIsHaveStock(false);
            }else {
                //走到这说明有对象,对这次遍历的VO对象的isHaveStock(是否有库存)来赋true
                skuLockVo.setIsHaveStock(true);
            }
        }

        //5.可以写锁单的代码了
        //简述一下实现思路,就是通过刚刚给VO对象的遍历赋值,如果里面夹杂了某些商品库存不够,即这些VO对象的isHaveStock为false,就不能锁单,否则就锁单
        //对VOList进行遍历,然后用流式处理中的anyMatch(只要有一个条件成立,就返回true),来实现判断逻辑
        /*
        我来解释一下anyMatch((skuLockVo) -> !skuLockVo.getIsHaveStock())这段代码的逻辑,
        首先再anyMatch中,会遍历voList,遍历到的skuLockVo就可以用来进行条件判断
        当(skuLockVo) -> ? 这个 ? 处的条件为true的时候,就会直接结束循环,返回true值
        但是由于skuLockVo.getIsHaveStock()中不满足库存条件的值为false,所以需要加一个!
         */
        if (skuLockVoList.stream().anyMatch((skuLockVo) -> !skuLockVo.getIsHaveStock())){
            //走到这说明有不满足库存的商品种类
            StringBuffer result = new StringBuffer();
            //获取没有库存的对象列表
            List<SkuLockVo> noIsHaveStock = skuLockVoList.stream().filter((skuLockVo) -> !skuLockVo.getIsHaveStock()).collect(Collectors.toList());

            for (SkuLockVo skuLockVo : noIsHaveStock) {
                result.append("商品" + skuLockVo.getSkuId() + "库存不足!");
            }

            //这时候就视为锁单失败,就需要释放分布式锁,让下次请求再来做业务判断
            redisTemplate.delete(lockKey);

            return result.toString();
        }else {
            //走到这说明voList里面的所有商品都满足库存条件了,可以写锁单代码了

            for (SkuLockVo skuLockVo : skuLockVoList) {
                //这段更新的sql语句,更新成功会返回1,失败会返回0
                int i = skuStockMapper.lock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
                if (i == 0){
                    //走到这说明数据库锁单失败
                    this.redisTemplate.delete(lockKey);
                    throw new ServiceException("库存锁定失败");
                }
            }

        }



        //往缓存里加入锁单的数据无需设置过期时间,因为后面的操作需要用队列来实现,如果队列操作的慢,缓存过期了就会引发BUG
        redisTemplate.opsForValue().set(dataKey,skuLockVoList);
        return "";
    }


}












